#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
云盘SDK基础客户端
定义统一的云盘操作接口，包括登录、鉴权、文件操作等
"""

from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Any, Union, BinaryIO
import logging
import os
from datetime import datetime


class StandardFileInfo:
    """标准化文件信息类
    
    用于统一不同云盘返回的文件信息格式
    """
    
    def __init__(self, **kwargs):
        # 基础信息
        self.id: str = kwargs.get('id', '')
        self.name: str = kwargs.get('name', '')
        self.path: str = kwargs.get('path', '')
        self.parent_id: str = kwargs.get('parent_id', '')
        
        # 文件属性
        self.size: int = kwargs.get('size', 0)
        self.type: str = kwargs.get('type', 'file')  # 'file' or 'folder'
        self.is_folder: bool = kwargs.get('is_folder', self.type == 'folder')
        
        # 时间信息
        self.create_time: str = kwargs.get('create_time', '')
        self.modify_time: str = kwargs.get('modify_time', '')
        self.modified_time: str = kwargs.get('modified_time', self.modify_time)  # 兼容字段
        
        # 文件哈希
        self.md5: str = kwargs.get('md5', '')
        self.sha1: str = kwargs.get('sha1', '')
        
        # 下载相关
        self.download_url: str = kwargs.get('download_url', '')
        self.thumbnail_url: str = kwargs.get('thumbnail_url', '')
        
        # 扩展信息
        self.mime_type: str = kwargs.get('mime_type', '')
        self.extension: str = kwargs.get('extension', '')
        
    def to_dict(self) -> Dict[str, Any]:
        """转换为字典格式"""
        return {
            'id': self.id,
            'name': self.name,
            'path': self.path,
            'parentId': self.parent_id,
            'size': self.size,
            'type': self.type,
            'isFolder': self.is_folder,
            'createTime': self.create_time,
            'modifyTime': self.modify_time,
            'modifiedTime': self.modified_time,
            'md5': self.md5,
            'sha1': self.sha1,
            'downloadUrl': self.download_url,
            'thumbnailUrl': self.thumbnail_url,
            'mimeType': self.mime_type,
            'extension': self.extension
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'StandardFileInfo':
        """从字典创建实例"""
        return cls(**data)


class FileInfoMapper:
    """文件信息映射器
    
    负责将不同云盘的文件信息格式转换为标准格式
    """
    
    @staticmethod
    def map_tianyi_file(file_data: Dict[str, Any]) -> StandardFileInfo:
        """映射天翼云盘文件信息"""
        return StandardFileInfo(
            id=str(file_data.get('id', '')),
            name=file_data.get('name', ''),
            path=file_data.get('path', ''),
            parent_id=str(file_data.get('parentId', '')),
            size=int(file_data.get('size', 0)),
            type='folder' if file_data.get('isFolder') else 'file',
            is_folder=bool(file_data.get('isFolder', False)),
            create_time=file_data.get('createDate', ''),
            modify_time=file_data.get('lastOpTime', ''),
            md5=file_data.get('md5', ''),
            download_url=file_data.get('downloadUrl', ''),
            mime_type=file_data.get('mediaType', '')
        )
    
    @staticmethod
    def map_aliyun_file(file_data: Dict[str, Any]) -> StandardFileInfo:
        """映射阿里云盘文件信息"""
        return StandardFileInfo(
            id=file_data.get('file_id', ''),
            name=file_data.get('name', ''),
            path=file_data.get('file_path', ''),
            parent_id=file_data.get('parent_file_id', ''),
            size=int(file_data.get('size', 0)),
            type=file_data.get('type', 'file'),
            is_folder=file_data.get('type') == 'folder',
            create_time=file_data.get('created_at', ''),
            modify_time=file_data.get('updated_at', ''),
            md5=file_data.get('content_hash', ''),
            download_url=file_data.get('download_url', ''),
            thumbnail_url=file_data.get('thumbnail', '')
        )
    
    @staticmethod
    def map_baidu_file(file_data: Dict[str, Any]) -> StandardFileInfo:
        """映射百度网盘文件信息"""
        return StandardFileInfo(
            id=str(file_data.get('fs_id', '')),
            name=file_data.get('filename', ''),
            path=file_data.get('path', ''),
            parent_id='',  # 百度网盘使用路径而非ID
            size=int(file_data.get('size', 0)),
            type='folder' if file_data.get('isdir') == 1 else 'file',
            is_folder=file_data.get('isdir') == 1,
            create_time=datetime.fromtimestamp(file_data.get('ctime', 0)).isoformat() if file_data.get('ctime') else '',
            modify_time=datetime.fromtimestamp(file_data.get('mtime', 0)).isoformat() if file_data.get('mtime') else '',
            md5=file_data.get('md5', '')
        )
    
    @staticmethod
    def map_onedrive_file(file_data: Dict[str, Any]) -> StandardFileInfo:
        """映射OneDrive文件信息"""
        return StandardFileInfo(
            id=file_data.get('id', ''),
            name=file_data.get('name', ''),
            path=file_data.get('webUrl', ''),
            parent_id=file_data.get('parentReference', {}).get('id', ''),
            size=int(file_data.get('size', 0)),
            type='folder' if 'folder' in file_data else 'file',
            is_folder='folder' in file_data,
            create_time=file_data.get('createdDateTime', ''),
            modify_time=file_data.get('lastModifiedDateTime', ''),
            md5=file_data.get('file', {}).get('hashes', {}).get('quickXorHash', ''),
            download_url=file_data.get('@microsoft.graph.downloadUrl', '')
        )
    
    @staticmethod
    def map_file_by_provider(provider: str, file_data: Dict[str, Any]) -> StandardFileInfo:
        """根据云盘提供商映射文件信息"""
        mapper_map = {
            'tianyi': FileInfoMapper.map_tianyi_file,
            'aliyun': FileInfoMapper.map_aliyun_file,
            'baidu': FileInfoMapper.map_baidu_file,
            'onedrive': FileInfoMapper.map_onedrive_file
        }
        
        mapper = mapper_map.get(provider.lower())
        if mapper:
            return mapper(file_data)
        else:
            # 默认映射，尝试通用字段
            return StandardFileInfo(
                id=str(file_data.get('id', file_data.get('file_id', ''))),
                name=file_data.get('name', file_data.get('filename', '')),
                path=file_data.get('path', ''),
                parent_id=str(file_data.get('parent_id', file_data.get('parentId', ''))),
                size=int(file_data.get('size', 0)),
                type=file_data.get('type', 'file'),
                is_folder=file_data.get('is_folder', file_data.get('isFolder', False)),
                create_time=file_data.get('create_time', file_data.get('createTime', '')),
                modify_time=file_data.get('modify_time', file_data.get('modifyTime', '')),
                md5=file_data.get('md5', '')
            )

class BaseCloudClient(ABC):
    """云盘客户端基类
    
    定义了所有云盘客户端必须实现的统一接口，包括：
    1. 登录认证相关：login, is_login, logout, get_user_info
    2. 令牌管理：get_token_info, set_token_info
    3. 文件列表操作：get_files, get_file_info
    4. 文件夹操作：create_folder, delete_folder, rename_folder
    5. 文件基础操作：upload_file, download_file, delete_file, rename_file, move_file, copy_file
    6. 文件秒传机制：check_fast_upload, fast_upload
    """
    
    def __init__(self, account_id: int, username: str, password: str = None, enable_debug: bool = False):
        self.account_id = account_id
        self.username = username
        self.password = password
        self.enable_debug = enable_debug
        
        # 设置日志
        self.logger = logging.getLogger(f"{self.__class__.__name__}_{account_id}")
        if enable_debug:
            self.logger.setLevel(logging.DEBUG)
        else:
            self.logger.setLevel(logging.INFO)
    
    # ==================== 登录认证相关 ====================
    
    @abstractmethod
    def login(self, username: str = None, password: str = None) -> bool:
        """登录云盘
        
        Args:
            username: 用户名（可选，默认使用初始化时的用户名）
            password: 密码（可选，默认使用初始化时的密码）
        
        Returns:
            bool: 登录是否成功
        """
        pass
    
    @abstractmethod
    def is_login(self) -> bool:
        """检查是否已登录
        
        Returns:
            bool: 是否已登录
        """
        pass
    
    @abstractmethod
    def check_login_status_only(self) -> bool:
        """仅检测登录状态，不进行自动登录
        
        Returns:
            bool: 是否已登录（基于现有token）
        """
        pass
    
    @abstractmethod
    def login_with_credentials(self, username: str = None, password: str = None) -> bool:
        """使用用户名密码进行初始登录
        
        Args:
            username: 用户名（可选，默认使用初始化时的用户名）
            password: 密码（可选，默认使用初始化时的密码）
        
        Returns:
            bool: 登录是否成功
        """
        pass
    
    @abstractmethod
    def get_user_info(self) -> Optional[Dict[str, Any]]:
        """获取用户信息
        
        Returns:
            Dict[str, Any]: 用户信息，包含存储容量、已用空间等信息
            {
                'loginName': str,      # 登录名
                'capacity': int,       # 总容量（字节）
                'available': int,      # 可用容量（字节）
                'used': int,          # 已用容量（字节）
                ...
            }
        """
        pass
    
    @abstractmethod
    def logout(self) -> bool:
        """登出
        
        Returns:
            bool: 登出是否成功
        """
        pass
    
    # ==================== 令牌管理 ====================
    
    @abstractmethod
    def get_token_info(self) -> Optional[Dict[str, Any]]:
        """获取当前的令牌信息
        
        Returns:
            Dict[str, Any]: 令牌信息，包含访问令牌、刷新令牌等
        """
        pass
    
    @abstractmethod
    def set_token_info(self, token_info: Dict[str, Any]) -> bool:
        """设置令牌信息
        
        Args:
            token_info: 令牌信息字典
            
        Returns:
            bool: 设置是否成功
        """
        pass
    
    # ==================== 文件列表操作 ====================
    
    @abstractmethod
    def get_files(self, folder_id: str = None, page_num: int = 1, page_size: int = 1000) -> Optional[Dict[str, Any]]:
        """获取文件列表
        
        Args:
            folder_id: 文件夹ID，None表示根目录
            page_num: 页码，从1开始
            page_size: 每页大小
        
        Returns:
            Dict[str, Any]: 文件列表数据
            {
                'files': List[Dict],     # 文件列表
                'folders': List[Dict],   # 文件夹列表
                'total': int,           # 总数量
                'page_num': int,        # 当前页码
                'page_size': int        # 每页大小
            }
        """
        pass
    
    def get_files_standardized(self, folder_id: str = None, page_num: int = 1, page_size: int = 1000) -> Optional[List[StandardFileInfo]]:
        """获取标准化格式的文件列表
        
        Args:
            folder_id: 文件夹ID，None表示根目录
            page_num: 页码，从1开始
            page_size: 每页大小
            
        Returns:
            List[StandardFileInfo]: 标准化文件信息列表
        """
        raw_data = self.get_files(folder_id, page_num, page_size)
        if not raw_data:
            return None
            
        provider = self.get_provider_name()
        standardized_files = []
        
        # 处理文件列表
        for file_data in raw_data.get('files', []):
            standardized_files.append(FileInfoMapper.map_file_by_provider(provider, file_data))
            
        # 处理文件夹列表
        for folder_data in raw_data.get('folders', []):
            # 确保文件夹数据包含isFolder标记
            folder_data_with_flag = folder_data.copy()
            folder_data_with_flag['isFolder'] = True
            standardized_files.append(FileInfoMapper.map_file_by_provider(provider, folder_data_with_flag))
            
        return standardized_files
    
    @abstractmethod
    def get_provider_name(self) -> str:
        """获取云盘提供商名称
        
        Returns:
            str: 提供商名称（如：tianyi, aliyun, baidu, onedrive）
        """
        pass
    
    @abstractmethod
    def get_file_info(self, file_id: str) -> Optional[Dict[str, Any]]:
        """获取文件详细信息
        
        Args:
            file_id: 文件ID
        
        Returns:
            Dict[str, Any]: 文件信息
            {
                'id': str,              # 文件ID
                'name': str,            # 文件名
                'size': int,            # 文件大小（字节）
                'type': str,            # 类型：'file' 或 'folder'
                'md5': str,             # 文件MD5（文件夹为空）
                'create_time': str,     # 创建时间
                'modify_time': str,     # 修改时间
                'download_url': str,    # 下载链接（如果支持）
                ...
            }
        """
        pass
    
    # ==================== 文件夹操作 ====================
    
    @abstractmethod
    def create_folder(self, parent_id: str, folder_name: str) -> Optional[str]:
        """创建文件夹
        
        Args:
            parent_id: 父文件夹ID
            folder_name: 文件夹名称
        
        Returns:
            str: 创建成功返回文件夹ID，失败返回None
        """
        pass
    
    @abstractmethod
    def delete_folder(self, folder_id: str) -> bool:
        """删除文件夹
        
        Args:
            folder_id: 文件夹ID
        
        Returns:
            bool: 删除是否成功
        """
        pass
    
    @abstractmethod
    def rename_folder(self, folder_id: str, new_name: str) -> bool:
        """重命名文件夹
        
        Args:
            folder_id: 文件夹ID
            new_name: 新名称
        
        Returns:
            bool: 重命名是否成功
        """
        pass
    
    # ==================== 文件基础操作 ====================
    
    @abstractmethod
    def upload_file(self, file_data: Union[str, bytes, BinaryIO], filename: str, 
                   parent_id: str = None, overwrite: bool = True, 
                   try_fast_upload: bool = True) -> Optional[Dict[str, Any]]:
        """上传文件
        
        Args:
            file_data: 文件数据（文件路径、字节数据或文件对象）
            filename: 文件名
            parent_id: 父文件夹ID，None表示根目录
            overwrite: 是否覆盖同名文件
            try_fast_upload: 是否尝试秒传
        
        Returns:
            Dict[str, Any]: 上传成功返回文件信息，失败返回None
        """
        pass
    
    @abstractmethod
    def download_file(self, file_id: str, save_path: str, filename: str = None) -> bool:
        """下载文件
        
        Args:
            file_id: 文件ID
            save_path: 保存路径（文件路径或目录路径）
            filename: 文件名（当save_path为目录时使用）
        
        Returns:
            bool: 下载是否成功
        """
        pass
    
    @abstractmethod
    def delete_file(self, file_id: str) -> bool:
        """删除文件
        
        Args:
            file_id: 文件ID
        
        Returns:
            bool: 删除是否成功
        """
        pass
    
    @abstractmethod
    def rename_file(self, file_id: str, new_name: str) -> bool:
        """重命名文件
        
        Args:
            file_id: 文件ID
            new_name: 新文件名
        
        Returns:
            bool: 重命名是否成功
        """
        pass
    
    @abstractmethod
    def move_file(self, file_id: str, target_folder_id: str) -> bool:
        """移动文件
        
        Args:
            file_id: 文件ID
            target_folder_id: 目标文件夹ID
        
        Returns:
            bool: 移动是否成功
        """
        pass
    
    @abstractmethod
    def copy_file(self, file_id: str, target_folder_id: str) -> bool:
        """复制文件
        
        Args:
            file_id: 文件ID
            target_folder_id: 目标文件夹ID
        
        Returns:
            bool: 复制是否成功
        """
        pass
    
    # ==================== 文件秒传机制 ====================
    
    @abstractmethod
    def check_fast_upload(self, filename: str, file_size: int, file_md5: str, 
                         parent_id: str = None) -> bool:
        """检查文件是否支持秒传
        
        Args:
            filename: 文件名
            file_size: 文件大小（字节）
            file_md5: 文件MD5值
            parent_id: 父文件夹ID
        
        Returns:
            bool: 是否支持秒传
        """
        pass
    
    @abstractmethod
    def fast_upload(self, filename: str, file_size: int, file_md5: str, 
                   parent_id: str = None) -> Optional[Dict[str, Any]]:
        """执行秒传
        
        Args:
            filename: 文件名
            file_size: 文件大小（字节）
            file_md5: 文件MD5值
            parent_id: 父文件夹ID
        
        Returns:
            Dict[str, Any]: 秒传成功返回文件信息，失败返回None
        """
        pass
    
    # ==================== 工具方法 ====================
    
    def calculate_file_md5(self, file_data: Union[str, bytes, BinaryIO]) -> str:
        """计算文件MD5值
        
        Args:
            file_data: 文件数据（文件路径、字节数据或文件对象）
        
        Returns:
            str: MD5值（小写）
        """
        import hashlib
        
        hash_md5 = hashlib.md5()
        
        if isinstance(file_data, str):
            # 文件路径
            with open(file_data, "rb") as f:
                for chunk in iter(lambda: f.read(4096), b""):
                    hash_md5.update(chunk)
        elif isinstance(file_data, bytes):
            # 字节数据
            hash_md5.update(file_data)
        elif hasattr(file_data, 'read'):
            # 文件对象
            current_pos = file_data.tell() if hasattr(file_data, 'tell') else 0
            if hasattr(file_data, 'seek'):
                file_data.seek(0)
            
            for chunk in iter(lambda: file_data.read(4096), b""):
                hash_md5.update(chunk)
            
            # 恢复原始位置
            if hasattr(file_data, 'seek'):
                file_data.seek(current_pos)
        else:
            raise ValueError("不支持的文件数据类型")
        
        return hash_md5.hexdigest().lower()
    
    def get_file_size(self, file_data: Union[str, bytes, BinaryIO]) -> int:
        """获取文件大小
        
        Args:
            file_data: 文件数据（文件路径、字节数据或文件对象）
        
        Returns:
            int: 文件大小（字节）
        """
        if isinstance(file_data, str):
            return os.path.getsize(file_data)
        elif isinstance(file_data, bytes):
            return len(file_data)
        elif hasattr(file_data, 'seek') and hasattr(file_data, 'tell'):
            current_pos = file_data.tell()
            file_data.seek(0, 2)  # 移动到文件末尾
            size = file_data.tell()
            file_data.seek(current_pos)  # 恢复原始位置
            return size
        else:
             raise ValueError("不支持的文件数据类型")